package com.gitee.wsl.android.ui.ext

//package com.dylanc.longan

import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Resources
import android.content.res.TypedArray
import android.graphics.*
import android.util.AttributeSet
import android.util.TypedValue
import android.view.*
import androidx.annotation.AttrRes
import androidx.annotation.StyleRes
import androidx.annotation.StyleableRes
import androidx.coordinatorlayout.widget.ViewGroupUtils
import androidx.core.content.withStyledAttributes
import androidx.core.graphics.applyCanvas
import androidx.core.graphics.drawable.toBitmap
import androidx.core.graphics.drawable.toDrawable
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.doOnAttach
import androidx.lifecycle.coroutineScope
import androidx.lifecycle.findViewTreeLifecycleOwner
//import androidx.viewbinding.ViewBinding
import com.gitee.wsl.android.ui.R
import com.gitee.wsl.android.ext.math.dp
import com.gitee.wsl.android.ext.base.relativeTo
import com.gitee.wsl.android.ui.ext.ViewExt.viewTags
import com.gitee.wsl.ext.noGetter
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.lang.Math.max
import kotlin.properties.ReadOnlyProperty
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

private var View.lastClickTime: Long? by viewTags(R.id.tag_last_click_time)

object ViewExt{
    fun <T> viewTags(key: Int) = object : ReadWriteProperty<View, T?> {
        @Suppress("UNCHECKED_CAST")
        override fun getValue(thisRef: View, property: KProperty<*>) =
            thisRef.getTag(key) as? T

        override fun setValue(thisRef: View, property: KProperty<*>, value: T?) =
            thisRef.setTag(key, value)
    }

    @Suppress("UNCHECKED_CAST")
    fun <T> viewTags(key: Int, block: View.() -> T) = ReadOnlyProperty<View, T> { thisRef, _ ->
        if (thisRef.getTag(key) == null) {
            thisRef.setTag(key, block(thisRef))
        }
        thisRef.getTag(key) as T
    }

    /**
     * 监听View绘制完成
     *
     * @param view
     * @param listener
     * @param removed 是否移除监听
     */
    @JvmStatic
    fun observeViewLayout(view: View, listener: ViewTreeObserver.OnGlobalLayoutListener?, removed: Boolean=true) {
        view.viewTreeObserver?.addOnGlobalLayoutListener(object :
            ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                if (removed) {
                    view.viewTreeObserver.removeGlobalOnLayoutListener(this)
                }
                listener?.onGlobalLayout()
            }
        })
    }
}


/*fun View.setUpLayoutParams(
    width: Int = ViewGroup.LayoutParams.MATCH_PARENT,
    height: Int = ViewGroup.LayoutParams.MATCH_PARENT
) {
    if (this.parent == null) return
    when (this.parent) {
        is LinearLayout -> {
            this.layoutParams = LinearLayout.LayoutParams(width, height)
        }
        is RelativeLayout -> {
            this.layoutParams = RelativeLayout.LayoutParams(width, height)
        }
        is FrameLayout -> {
            this.layoutParams = FrameLayout.LayoutParams(width, height)
        }
        is ConstraintLayout -> {
            this.layoutParams = ConstraintLayout.LayoutParams(width, height)
        }
        else -> {
            this.layoutParams = ViewGroup.LayoutParams(width, height)
        }
    }
    requestLayout()
}*/


fun View.changeSize(width: Int = -1, height: Int = -1) {
    if (width != -1) {
        this.layoutParams?.width = width
    }
    if (height != -1) {
        this.layoutParams?.height = height
    }
}

fun List<View>.doOnClick(clickIntervals: Int, isSharingIntervals: Boolean = false, block: () -> Unit) =
    forEach { it.doOnClick(clickIntervals, isSharingIntervals, block) }

fun View.doOnClick(clickIntervals: Int, isSharingIntervals: Boolean = false, block: () -> Unit) =
    setOnClickListener {
        val view = if (isSharingIntervals) context.activity?.window?.decorView ?: this else this
        val currentTime = System.currentTimeMillis()
        val lastTime = view.lastClickTime ?: 0L
        if (currentTime - lastTime > clickIntervals) {
            view.lastClickTime = currentTime
            block()
        }
    }

inline fun List<View>.doOnClick(crossinline block: () -> Unit) =
    forEach { it.doOnClick(block) }

inline fun View.doOnClick(crossinline block: () -> Unit) =
    setOnClickListener { block() }

fun List<View>.doOnLongClick(clickIntervals: Int, isSharingIntervals: Boolean = false, block: () -> Unit) =
    forEach { it.doOnLongClick(clickIntervals, isSharingIntervals, block) }

fun View.doOnLongClick(clickIntervals: Int, isSharingIntervals: Boolean = false, block: () -> Unit) =
    doOnLongClick {
        val view = if (isSharingIntervals) context.activity?.window?.decorView ?: this else this
        val currentTime = System.currentTimeMillis()
        val lastTime = view.lastClickTime ?: 0L
        if (currentTime - lastTime > clickIntervals) {
            view.lastClickTime = currentTime
            block()
        }
    }

inline fun List<View>.doOnLongClick(crossinline block: () -> Unit) =
    forEach { it.doOnLongClick(block) }

inline fun View.doOnLongClick(crossinline block: () -> Unit) =
    setOnLongClickListener {
        block()
        true
    }

var View.roundCorners: Float
    get() = noGetter()
    set(value) {
        clipToOutline = true
        outlineProvider = object : ViewOutlineProvider() {
            override fun getOutline(view: View, outline: Outline) {
                outline.setRoundRect(0, 0, view.width, view.height, value)
            }
        }
    }


fun View?.isTouchedAt(x: Float, y: Float): Boolean =
    isTouchedAt(x.toInt(), y.toInt())

fun View?.isTouchedAt(x: Int, y: Int): Boolean =
    this?.locationOnScreen?.run { x in left..right && y in top..bottom } ?: false

fun View.findTouchedChild(x: Float, y: Float): View? =
    findTouchedChild(x.toInt(), y.toInt())

fun View.findTouchedChild(x: Int, y: Int): View? =
    touchables.find { it.isTouchedAt(x, y) }

/**
 * Computes the coordinates of this view on the screen.
 */
inline val View.locationOnScreen: Rect
    get() = IntArray(2).let {
        getLocationOnScreen(it)
        Rect(it[0], it[1], it[0] + width, it[1] + height)
    }

val View.rootWindowInsetsCompat: WindowInsetsCompat? by viewTags(R.id.tag_root_window_insets) {
    ViewCompat.getRootWindowInsets(this)
}

val View.windowInsetsControllerCompat: WindowInsetsControllerCompat? by viewTags(R.id.tag_window_insets_controller) {
    ViewCompat.getWindowInsetsController(this)
}


inline fun View.withStyledAttributes(
    set: AttributeSet?,
    @StyleableRes attrs: IntArray,
    @AttrRes defStyleAttr: Int = 0,
    @StyleRes defStyleRes: Int = 0,
    block: TypedArray.() -> Unit
) =
    context.withStyledAttributes(set, attrs, defStyleAttr, defStyleRes, block)


fun View.doOnApplyWindowInsets(action: (View, WindowInsetsCompat) -> WindowInsetsCompat) =
    ViewCompat.setOnApplyWindowInsetsListener(this, action)



/**
 * Check whether the view is visible to the user.
 * This function works for the following scenario:
 * 1.The switch of power button,
 * 2.Home clicked
 * 3.Invoke [View.setVisibility] manually,
 * 4.Covered by another [View] in the same view tree,
 * 5.[ViewPager]'s scrolling,
 * 6.[ScrollView]'s scrolling,
 * 7.[NestedScrollView]'s scrolling,
 * 8.[Dialog]'s showing,
 * 9.[DialogFragment]'s showing,
 * 10.[Activity]'s switching
 * 11.[Fragment]'s switching
 *
 * But it is not recommend to use it in the child view of [ScrollView] or [NestedScrollView] due to the performance issue.
 * Too many child lead to too many scroll listeners.
 *
 * @param viewGroups the parent ViewGroups which new view to be added in (like Fragment's Container View)
 * @param needScrollListener whether detects scroll or not
 * @param block a lambda to tell whether the current View is visible to user
 */
fun View.onVisibilityChange(
    viewGroups: List<ViewGroup>? = null,
    needScrollListener: Boolean = true,
    block: (view: View, isVisible: Boolean) -> Unit
) {
    val KEY_VISIBILITY = "KEY_VISIBILITY".hashCode()
    val KEY_HAS_LISTENER = "KEY_HAS_LISTENER".hashCode()
    if (getTag(KEY_HAS_LISTENER) == true) return

    val visibleRect = Rect()

    val checkVisibility = {
        val lastVisibility = getTag(KEY_VISIBILITY) as? Boolean
        val isInScreen = visiblePercent(visibleRect) > 0f
        if (lastVisibility == null) {
            if (isInScreen) {
                block(this, true)
                setTag(KEY_VISIBILITY, true)
            }
        } else if (lastVisibility != isInScreen) {
            block(this, isInScreen)
            setTag(KEY_VISIBILITY, isInScreen)
        }
    }

    class LayoutListener : ViewTreeObserver.OnGlobalLayoutListener {
        var addedView: View? = null
        override fun onGlobalLayout() {
            if (addedView != null) {
                val addedRect = Rect().also { addedView?.getGlobalVisibleRect(it) }
                val rect = Rect().also { this@onVisibilityChange.getGlobalVisibleRect(it) }
                if (addedRect.contains(rect)) {
                    block(this@onVisibilityChange, false)
                    setTag(KEY_VISIBILITY, false)
                } else {
                    block(this@onVisibilityChange, true)
                    setTag(KEY_VISIBILITY, true)
                }
            } else {
                checkVisibility()
            }
        }
    }

    val layoutListener = LayoutListener()
    viewGroups?.forEachIndexed { index, viewGroup ->
        viewGroup.setOnHierarchyChangeListener(object : ViewGroup.OnHierarchyChangeListener {
            override fun onChildViewAdded(parent: View?, child: View?) {
                layoutListener.addedView = child
            }

            override fun onChildViewRemoved(parent: View?, child: View?) {
                layoutListener.addedView = null
            }
        })
    }
    viewTreeObserver.addOnGlobalLayoutListener(layoutListener)

    var scrollListener: ViewTreeObserver.OnScrollChangedListener? = null
    if (needScrollListener) {
        scrollListener = ViewTreeObserver.OnScrollChangedListener { checkVisibility() }
        viewTreeObserver.addOnScrollChangedListener(scrollListener)
    }

    val focusChangeListener = ViewTreeObserver.OnWindowFocusChangeListener { hasFocus ->
        val lastVisibility = getTag(KEY_VISIBILITY) as? Boolean
        val isInScreen = visiblePercent(visibleRect) > 0
        if (hasFocus) {
            if (lastVisibility != isInScreen) {
                block(this, isInScreen)
                setTag(KEY_VISIBILITY, isInScreen)
            }
        } else {
            if (lastVisibility == true) {
                block(this, false)
                setTag(KEY_VISIBILITY, false)
            }
        }
    }
    viewTreeObserver.addOnWindowFocusChangeListener(focusChangeListener)

    addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
        override fun onViewAttachedToWindow(v: View) {
        }

        override fun onViewDetachedFromWindow(v: View) {
            v ?: return
            // delay by post for giving the last execution chance to OnGlobalLayoutListener
            post {
                try {
                    v.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
                } catch (_: java.lang.Exception) {
                    v.viewTreeObserver.removeGlobalOnLayoutListener(layoutListener)
                }
                v.viewTreeObserver.removeOnWindowFocusChangeListener(focusChangeListener)
                if (scrollListener != null) v.viewTreeObserver.removeOnScrollChangedListener(scrollListener)
                viewGroups?.forEach { it.setOnHierarchyChangeListener(null) }
            }
            removeOnAttachStateChangeListener(this)
        }
    })
    setTag(KEY_HAS_LISTENER, true)
}

val View.isInScreen: Boolean
    get() = ViewCompat.isAttachedToWindow(this) && visibility == View.VISIBLE && getLocalVisibleRect(Rect())

/**
 * Check whether the view is visible to user
 */
fun View.visiblePercent(rect: Rect): Float {
    val isVisible = getLocalVisibleRect(rect)
    return if (ViewCompat.isAttachedToWindow(this) && visibility == View.VISIBLE && isVisible) {
        rect.let { it.width() * it.height() }.toFloat() / (width * height)
    } else 0f
}


/**
 * get relative position of this [View] relative to [otherView]
 */
fun View.getRelativeRectTo(otherView: View): Rect {
    val parentRect = Rect().also { otherView.getGlobalVisibleRect(it) }
    val childRect = Rect().also { getGlobalVisibleRect(it) }
    return childRect.relativeTo(parentRect)
}


@SuppressLint("ClickableViewAccessibility")
fun View.extraAnimClickListener(animator: ValueAnimator, action: (View) -> Unit) {
    setOnTouchListener { v, event ->
        when (event.action) {
            MotionEvent.ACTION_DOWN -> animator.start()
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> animator.reverse()
        }
        false
    }

    setOnClickListener { action(this) }
}

val SHADOW_LEFT: Int
    get() = 1.shl(1)
val SHADOW_RIGHT: Int
    get() = 1.shl(2)
val SHADOW_BOTTOM: Int
    get() = 1.shl(3)
val SHADOW_TOP: Int
    get() = 1.shl(4)

/**
 * add shadow under this [View]
 */
fun View.shadow(
    direction: Int = SHADOW_LEFT.or(SHADOW_BOTTOM).or(SHADOW_RIGHT),
    radius: Int = 5.dp,
    color: Int = Color.parseColor("#3c000000")
) {
    doOnAttach {
        post {
            // keep the origin background here
            val backgroundBitmap = background.let { it.toBitmap(max(width, 1), max(height, 1)) }
            var extraPaddingRight = 0
            var extraPaddingLeft = 0
            var extraPaddingBottom = 0
            var extraPaddingTop = 0

            // calculate extra space for shadow
            var extraWidth = 0
            extraWidth += if (direction.and(SHADOW_LEFT) == SHADOW_LEFT) {
                extraPaddingLeft = radius
                radius
            } else 0
            extraWidth += if (direction.and(SHADOW_RIGHT) == SHADOW_RIGHT) {
                extraPaddingRight = radius
                radius
            } else 0
            var extraHeight: Int = 0
            extraHeight += if (direction.and(SHADOW_BOTTOM) == SHADOW_BOTTOM) {
                extraPaddingBottom = radius
                radius
            } else 0
            extraHeight += if (direction.and(SHADOW_TOP) == SHADOW_TOP) {
                extraPaddingTop = radius
                radius
            } else 0

            //shift view by set padding according to the shadow radius
            val paddingLeft = paddingLeft
            val paddingRight = paddingRight
            val paddingBottom = paddingBottom
            val paddingTop = paddingTop
            setPadding(
                paddingLeft + extraPaddingLeft,
                paddingTop + extraPaddingTop,
                paddingRight + extraPaddingRight,
                paddingBottom + extraPaddingBottom
            )
            val shadowWidth = width + extraWidth
            val shadowHeight = height + extraHeight

            /**
             * create a bitmap which is bigger than the origin background of this view, the extra space is used to draw shadow
             * 1. draw shadow firstly
             * 2. draw the origin background upon shadow
             */
            Bitmap.createBitmap(shadowWidth, shadowHeight, Bitmap.Config.ARGB_8888).applyCanvas {
                val shadowPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
                    maskFilter = BlurMaskFilter(radius.toFloat(), BlurMaskFilter.Blur.NORMAL)
                    this.color = color
                }
                drawRect(Rect(radius, radius, width - radius, height - radius), shadowPaint)
                drawBitmap(
                    backgroundBitmap,
                    null,
                    Rect(extraPaddingLeft, extraPaddingTop, width - extraPaddingRight, height - extraPaddingBottom),
                    Paint()
                )
            }.let {
                // renew view's background
                background = it.toDrawable(resources)
            }
        }
    }
}


@SuppressLint("RestrictedApi")
fun View.expand(dx: Int, dy: Int) {
    class MultiTouchDelegate(bound: Rect? = null, delegateView: View) : TouchDelegate(bound, delegateView) {
        val delegateViewMap = mutableMapOf<View, Rect>()
        private var delegateView: View? = null

        override fun onTouchEvent(event: MotionEvent): Boolean {
            val x = event.x.toInt()
            val y = event.y.toInt()
            var handled = false
            when (event.actionMasked) {
                MotionEvent.ACTION_DOWN -> {
                    delegateView = findDelegateViewUnder(x, y)
                }
                MotionEvent.ACTION_CANCEL -> {
                    delegateView = null
                }
            }
            delegateView?.let {
                event.setLocation(it.width / 2f, it.height / 2f)
                handled = it.dispatchTouchEvent(event)
            }
            return handled
        }

        private fun findDelegateViewUnder(x: Int, y: Int): View? {
            delegateViewMap.forEach { entry -> if (entry.value.contains(x, y)) return entry.key }
            return null
        }
    }

    val parentView = parent as? ViewGroup
    parentView ?: return

    if (parentView.touchDelegate == null) parentView.touchDelegate = MultiTouchDelegate(delegateView = this)
    post {
        val rect = Rect()
        ViewGroupUtils.getDescendantRect(parentView, this, rect)
        rect.inset(-dx, -dy)
        (parentView.touchDelegate as? MultiTouchDelegate)?.delegateViewMap?.put(this, rect)
    }
}

 fun View.visible() {
   visibility = View.VISIBLE
}

fun View.gone() {
    visibility = View.GONE
}

fun View.hide() {
    visibility = View.INVISIBLE
}

fun View.enable() {
    isEnabled = true
}

fun View.disable() {
    isEnabled = false
}


/**
 * provide safer access to {@link View#postDelay(Runnable action, long delayMillis)}
 */
fun View.postDelayByLifecycle(
    block: () -> Unit,
    delayMillis: Long,
    dispatcher: CoroutineDispatcher = Dispatchers.Main
): Job? = findViewTreeLifecycleOwner()?.let { lifecycleOwner ->
    lifecycleOwner.lifecycle.coroutineScope.launch(dispatcher) {
        delay(delayMillis)
        block()
    }
}

/**
 * Increase the click area of this view
 */
fun View.increaseHitArea(dp: Float) {
    // increase the hit area
    val increasedArea = TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP,
        dp,
        Resources.getSystem().displayMetrics
    ).toInt()
    val parent = parent as View
    parent.post {
        val rect = Rect()
        getHitRect(rect)
        rect.top -= increasedArea
        rect.left -= increasedArea
        rect.bottom += increasedArea
        rect.right += increasedArea
        parent.touchDelegate = TouchDelegate(rect, this)
    }
}

fun View.setMargins(
    leftMarginDp: Int? = null,
    topMarginDp: Int? = null,
    rightMarginDp: Int? = null,
    bottomMarginDp: Int? = null
) {
    if (layoutParams is ViewGroup.MarginLayoutParams) {
        val params = layoutParams as ViewGroup.MarginLayoutParams
        leftMarginDp?.run { params.leftMargin = this.dp }
        topMarginDp?.run { params.topMargin = this.dp }
        rightMarginDp?.run { params.rightMargin = this.dp }
        bottomMarginDp?.run { params.bottomMargin = this.dp }
        requestLayout()
    }
}

fun View.clearMargins() {
    if (layoutParams is ViewGroup.MarginLayoutParams) {
        val params = layoutParams as ViewGroup.MarginLayoutParams
        params.leftMargin = 0.dp
        params.topMargin = 0.dp
        params.rightMargin = 0.dp
        params.bottomMargin = 0.dp
        requestLayout()
    }
}

fun View.visibleWithAnimation() {
    this.animate().alpha(1f)
    visible()
}

fun View.hideWithAnimation() {
    this.animate().alpha(0f)
    hide()
}

fun View.hideWithoutAnimation() {
    this.alpha = 0f
    hide()
}


fun View.goneWithAnimation() {
    this.animate().alpha(0f)
    hide()
}

fun View.goneWithDelay(delay: Long,endOfAnimation: ()->Unit) {
    this.animate()
        .setStartDelay(delay)
        .withEndAction {
            hide()
            endOfAnimation()
        }
        .start()
}

fun View.takeScreenshot(): Bitmap {
    val bitmap = Bitmap.createBitmap(this.width, this.height, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(bitmap)
    val bgDrawable = this.background
    if (bgDrawable != null) {
        bgDrawable.draw(canvas)
    } else {
        canvas.drawColor(Color.WHITE)
    }
    this.draw(canvas)
    return bitmap
}


/**
 * Created by ve3344@qq.com.
 */
/*
open class VirtualViewBinding<V : View>(val contentView: V) : ViewBinding {
    override fun getRoot(): View = contentView
}

fun <T : View> viewBindingOf(factory: (Context) -> T): ((inflater: LayoutInflater, root: ViewGroup, attachToRoot: Boolean) -> VirtualViewBinding<T>) =
    { _, root, attach ->
        val rootView = factory(root.context)
        if (attach) {
            root.addView(rootView)
        }
        VirtualViewBinding(rootView)
    }*/
